using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using Microsoft.Data.Schema.Extensibility;
using Microsoft.Data.Schema.SchemaModel;
using Microsoft.Data.Schema.ScriptDom.Sql;
using Microsoft.Data.Schema.Sql.SchemaModel;
using Microsoft.Data.Schema.Sql;
using Microsoft.Data.Schema.StaticCodeAnalysis;


using System.IO;
using System.Linq;

namespace Neznayka
{
    /// <summary>
    /// This is a SQL rule which returns a warning message 
    /// whenever there is a varible in a routine taht is only ever written to.
    /// </summary>

    [DatabaseSchemaProviderCompatibility(typeof(SqlDatabaseSchemaProvider))]
    [DataRuleAttribute(
        NeznaykaConstants.NameSpace,
        NeznaykaConstants.AvoidWriteOnlyVariablesRuleId,
        NeznaykaConstants.ResourceBaseName,
        NeznaykaConstants.AvoidWriteOnlyVariables_RuleName,
        NeznaykaConstants.CategoryVariableUsage,
        DescriptionResourceId = NeznaykaConstants.AvoidWriteOnlyVariables_ProblemDescription)]
    [SupportedElementType(typeof(ISqlFunction))]
    [SupportedElementType(typeof(ISqlTableValuedFunction))]
    [SupportedElementType(typeof(ISqlMultiStatementTableValuedFunction))]
    [SupportedElementType(typeof(ISqlProcedure))]
    [SupportedElementType(typeof(ISqlTrigger))]
    public class AvoidWriteOnlyVariablesRule : StaticCodeAnalysisRule
    {
        #region Overrides
        /// <summary>
        /// Analyze the model element
        /// </summary>
        public override IList<DataRuleProblem> Analyze(DataRuleSetting ruleSetting, DataRuleExecutionContext context)
        {

            // (Re)-Load Environment settings
            List<DataRuleProblem> problems;
            SqlSchemaModel sqlSchemaModel;
            ISqlModelElement sqlElement;
            TSqlFragment sqlFragment;
            DMVRuleSetup.RuleSetup(context, out problems, out sqlSchemaModel, out sqlElement, out sqlFragment);

            // visitor to get the declarations of variables
            VariableDeclarationVisitor declarationVisitor = new VariableDeclarationVisitor();
            sqlFragment.Accept(declarationVisitor);
            List<Literal> variableDeclarations = declarationVisitor.VariableDeclarations;

            // visitor to get parameter names - these look like variables and need removing
            // from variable references before we use them
            NamedParameterUsageVisitor namedParameterUsageVisitor = new NamedParameterUsageVisitor();
            sqlFragment.Accept(namedParameterUsageVisitor);
            IEnumerable<Literal> namedParameters = namedParameterUsageVisitor.NamedParameters;

            // visitor to get the occurrences of variables
            VariableUsageVisitor usageVisitor = new VariableUsageVisitor();
            sqlFragment.Accept(usageVisitor);
            IEnumerable<Literal> allVariableLikeReferences = usageVisitor.VariableReferences;

            // get all assignments to variables
            UpdatedVariableVisitor updatedVariableVisitor = new UpdatedVariableVisitor();
            sqlFragment.Accept(updatedVariableVisitor);
            List<SQLExpressionDependency> setVariables = updatedVariableVisitor.SetVariables;

            // remove all named parameters from the list of referenced variables
            IEnumerable<Literal> tmpVR = allVariableLikeReferences.Except(namedParameters, new SqlLiteralComparer());
            List<Literal> variableReferences = tmpVR.ToList();

            //obsolete
            //// get all assignments to variables
            //List<Literal> variableWriteOccurrences = SqlRuleUtils.SetVariableAbstractions(sqlFragment) ;

            //Hashtable objects = new Hashtable();
            //Hashtable counts = new Hashtable();
            //Hashtable writeCounts = new Hashtable();
            Dictionary<string, object> objects = new Dictionary<string, object>(SqlComparer.Comparer);
            Dictionary<string, int> counts = new Dictionary<string, int>(SqlComparer.Comparer);
            Dictionary<string, int> writeCounts = new Dictionary<string, int>(SqlComparer.Comparer);

            foreach (Literal variableDeclaration in variableDeclarations)
            {
                objects.Add(variableDeclaration.Value,variableDeclaration);
            }

            bool FixPointReached = false;
            while (!FixPointReached)
            {
                foreach (Literal variableReference in variableReferences)
                {
                    if (!counts.ContainsKey(variableReference.Value))
                    {
                        counts.Add(variableReference.Value, 1);
                    }
                    else
                    {
                        counts[variableReference.Value]++;
                    }
                }


                // Adjust write count by 1
                // And ref count down by however many occurences of the target variable
                // appear in the source expression.  These are not independent
                // ref occurrences and need to be adjusted out.
                foreach (SQLExpressionDependency variableWriteOccurrence in setVariables)
                {
                    Literal targetVariable = variableWriteOccurrence.Variable;
                    int refCount = 0;

//                    refCount -= variableWriteOccurrence.Dependencies.ToList().FindAll(e => e.Value == targetVariable.Value).Count;
                    refCount -= variableWriteOccurrence.Dependencies.ToList().FindAll(e => SqlComparer.CompareEqual(e.Value, targetVariable.Value)).Count;


                    if (!writeCounts.ContainsKey(targetVariable.Value))
                    {
                        writeCounts.Add(targetVariable.Value, 1);
                    }
                    else
                    {
                        writeCounts[targetVariable.Value]++;
                    }
                    //writeCounts[targetVariable.Value] = writeCounts[targetVariable.Value] == null
                    //                                ? 1
                    //                                : ((int)writeCounts[targetVariable.Value]) + 1;

                    if (!counts.ContainsKey(targetVariable.Value))
                    {
                        counts.Add(targetVariable.Value, 0);
                    }
                    else
                    {
                        counts[targetVariable.Value] = counts[targetVariable.Value] + refCount;
                    }
                    //counts[targetVariable.Value] = counts[targetVariable.Value] == null
                    //                                ? 0
                    //                                : ((int)counts[targetVariable.Value]) + refCount;

                }

                List<string> DeletedKeys = new List<string>();

                foreach (string key in objects.Keys)
                {
                    if (counts.ContainsKey(key) && writeCounts.ContainsKey(key))
                    {
                        if (counts[key] - writeCounts[key] == 1)
                        {
                            DataRuleProblem problem = new DataRuleProblem(this,
                                                        String.Format(CultureInfo.CurrentCulture, this.RuleProperties.Description, SqlRuleUtils.GetElementName(sqlSchemaModel, sqlElement)),
                                                        sqlElement);

                            SqlRuleUtils.UpdateProblemPosition(problem, ((Literal)objects[key]).StartOffset, ((Literal)objects[key]).FragmentLength);
                            problems.Add(problem);

                            DeletedKeys.Add(key);
                        }
                    }
                }

                // remove variables no longer in play
                foreach (string deletedKey in DeletedKeys)
                {
                    // remove all the dependent variable occurrences from the list of all variable occurrences
//                    foreach (SQLExpressionDependency ed in setVariables.FindAll(e => e.Variable.Value == deletedKey))
                    foreach (SQLExpressionDependency ed in setVariables.FindAll(e => SqlComparer.CompareEqual(e.Variable.Value, deletedKey)))
                        {

                        foreach (Literal d in ed.Dependencies)
                        {
                            variableReferences.RemoveAll(vr => SqlComparisonUtils.Equals(vr, d));
                        }
                    }
                    // remove the variable itself
//                    variableReferences.RemoveAll(e => e.Value == deletedKey);
                    variableReferences.RemoveAll(e => SqlComparer.CompareEqual(e.Value, deletedKey));

                    //remove the entry from the list of variable assignments
//                    setVariables.RemoveAll(e => e.Variable.Value == deletedKey);
                    setVariables.RemoveAll(e => SqlComparer.CompareEqual(e.Variable.Value, deletedKey));

                    objects.Remove(deletedKey);
                    counts.Remove(deletedKey);
                    writeCounts.Remove(deletedKey);
                }
                // reset all counts
                foreach (string key in objects.Keys)
                {
                    if (counts.ContainsKey(key))
                    {
                        counts[key] = 1;
                    }
                    if (writeCounts.ContainsKey(key))
                    {
                        writeCounts[key] = 1;
                    }
                }

                // quit if we did nothing 
                if ( DeletedKeys.Count == 0 ) { 
                    FixPointReached = true ;
                }
            }

            return problems;
        }

        #endregion    
 
    }
}